package com.central.oauth.config;

import com.central.common.config.DefaultPasswordConfig;
import com.central.common.constant.SecurityConstants;
import com.central.common.properties.TenantProperties;
import com.central.oauth.filter.LoginProcessSetTenantFilter;
import com.central.oauth.handler.OauthLoginAuthenticationFailureHandler;
import com.central.oauth.handler.OauthLogoutSuccessHandler;
import com.central.oauth.mobile.MobileAuthenticationSecurityConfig;
import com.central.oauth.mobile.MobileCodeAuthenticationSecurityConfig;
import com.central.oauth.openid.OpenIdAuthenticationSecurityConfig;
import com.central.oauth.service.IValidateCodeService;
import com.central.oauth.tenant.TenantAuthenticationSecurityConfig;
import com.central.oauth.tenant.TenantUsernamePasswordAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;

import javax.annotation.Resource;

/**
 * spring security 配置（认证）先认证（账号登录）之后，才能够授权，所以这里先走 ss 认证，
 * 然后去授权 com.central.oauth.config.AuthorizationServerConfig
 * <p>
 * 在WebSecurityConfigurerAdapter不拦截oauth要开放的资源
 *
 * @author ly
 */
@Configuration
@Import(DefaultPasswordConfig.class)
// @EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired(required = false)
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Resource
    private LogoutHandler oauthLogoutHandler;

    @Autowired
    private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;

    @Autowired
    private MobileAuthenticationSecurityConfig mobileAuthenticationSecurityConfig;

    @Autowired
    private MobileCodeAuthenticationSecurityConfig mobileCodeAuthenticationSecurityConfig;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TenantAuthenticationSecurityConfig tenantAuthenticationSecurityConfig;

    @Autowired
    private TenantProperties tenantProperties;

    @Autowired
    private IValidateCodeService validateCodeService;

    /**
     * 这一步的配置是必不可少的，否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
     *
     * @return 认证管理对象
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public TenantUsernamePasswordAuthenticationFilter tenantAuthenticationFilter(
            AuthenticationManager authenticationManager) {
        TenantUsernamePasswordAuthenticationFilter filter = new TenantUsernamePasswordAuthenticationFilter(
                validateCodeService);
        filter.setAuthenticationManager(authenticationManager);
        filter.setFilterProcessesUrl(SecurityConstants.OAUTH_LOGIN_PRO_URL);
        // 成功走这里
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        // filter.setAuthenticationFailureHandler(
        //         new SimpleUrlAuthenticationFailureHandler(SecurityConstants.LOGIN_FAILURE_PAGE));
        // 认证失败处理
        filter.setAuthenticationFailureHandler(new OauthLoginAuthenticationFailureHandler());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                //授权服务器关闭basic认证
                .permitAll()
                .and()
                .logout()
                .logoutUrl(SecurityConstants.LOGOUT_URL)
                .logoutSuccessHandler(new OauthLogoutSuccessHandler())
                .addLogoutHandler(oauthLogoutHandler)
                .clearAuthentication(true)
                .and()
                // 自定义登录方式生效
                .apply(openIdAuthenticationSecurityConfig)
                .and()
                .apply(mobileAuthenticationSecurityConfig)
                .and()
                .apply(mobileCodeAuthenticationSecurityConfig)
                .and()
                .addFilterBefore(new LoginProcessSetTenantFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf().disable()
                // 解决不允许显示在iframe的问题
                .headers().frameOptions().disable().cacheControl();

        if (tenantProperties.getEnable()) {
            /*
            formlogin 登录认证模式的三要素：
            1 登录认证逻辑：登录url、如何接收登录参数（username、password）、登录成功后的逻辑（静态）；
            2 资源访问控制规则：决定什么用户、什么角色可以访问什么资源（一般动态数据库）；
            3 用户具有角色权限：配置某个用户拥有什么角色、什么权限（动态数据库）
             */
            //解决不同租户单点登录时角色没变化
            http.formLogin()
                    // "/login.html"，一旦用户的请求没有权限就跳转到这个页面（登录页面）
                    .loginPage(SecurityConstants.LOGIN_PAGE)
                    .and()
                    // 在同一个位置加入自定义的filter,同个位置的filter顺序不确定,
                    .addFilterAt(tenantAuthenticationFilter(authenticationManager),
                            UsernamePasswordAuthenticationFilter.class)
                    .apply(tenantAuthenticationSecurityConfig);
        } else {
            // 进入登录界面，
            http.formLogin()
                    // "/login.html"，一旦用户的请求没有权限就跳转到这个页面（登录页面）
                    .loginPage(SecurityConstants.LOGIN_PAGE)
                    // "/user/login" 监控地址,登录表单form中action的地址，也就是处理认证请求的路径
                    .loginProcessingUrl(SecurityConstants.OAUTH_LOGIN_PRO_URL)
                    ///登录表单form中用户名输入框input的name名，不修改的话默认是username
                    // .usernameParameter("username")
                    //form中密码输入框input的name名，不修改的话默认是password
                    // .passwordParameter("password")
                    .successHandler(authenticationSuccessHandler);
        }

        // 基于密码 等模式可以无session,不支持授权码模式
        if (authenticationEntryPoint != null) {
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        } else {
            // 授权码模式单独处理，需要session的支持，此模式可以支持所有oauth2的认证
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        }
    }

    /**
     * 全局用户信息，认证的用户信息来自数据库
     */
    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
}
